/**
 * Copyright (C), 2018 store
 * Encoding: UTF-8
 * Date: 19-9-30 上午11:19
 * History:
 */
package com.swak.frame.hystrix.command;

import java.lang.reflect.Method;

import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandKey;
import com.netflix.hystrix.HystrixCommandProperties;
import com.netflix.hystrix.HystrixThreadPoolKey;
import com.netflix.hystrix.HystrixThreadPoolProperties;
import com.netflix.hystrix.contrib.javanica.utils.AopUtils;
import com.swak.frame.aop.AspectAopUtils;
import com.swak.frame.command.SwakMethodExecution;
import com.swak.frame.command.SwakMethodProvider;
import com.swak.frame.command.annotation.SwakExecutionType;
import com.swak.frame.command.fallback.SwakFallbackMethod;
import com.swak.frame.common.SpringBeanFactory;
import com.swak.frame.hystrix.annotation.SwakHystrix;
import com.swak.frame.hystrix.event.FallbackEvent;
import com.swak.frame.hystrix.event.HystrixEventInfo;
import com.swak.frame.hystrix.exception.SwakExecutionException;
import com.swak.frame.hystrix.exception.SwakFallBackException;


/**
 * MarsHystrixCommand.java
 *
 * @author ColleyMa
 * @version 19-9-30 上午11:19
 */
public class SwakHystrixCommand extends HystrixCommand<Object> {
    protected Logger logger = LoggerFactory.getLogger(getClass()); // 日志记录
    private ProceedingJoinPoint joinPoint;
    private SwakExecutionType executionType;

    private SwakHystrix marsHystrix;

    public SwakHystrixCommand(ProceedingJoinPoint joinPoint, SwakExecutionType executionType,
        SwakHystrix marsHystrix) {
        super(setter(joinPoint, marsHystrix)); // 线程池为30
        this.joinPoint = joinPoint;
        this.executionType = executionType;
        this.marsHystrix = marsHystrix;
    }

    @Override
    protected Object run() throws Exception {
        Method method = AopUtils.getMethodFromTarget(joinPoint);
        Object obj = joinPoint.getTarget();
        Object[] args = joinPoint.getArgs();
        SwakMethodExecution methodExecution = new SwakMethodExecution(obj, method, args);
        try {
            return methodExecution.executeWithArgs(executionType, args);
        } catch (Throwable e) {
            if (e instanceof RuntimeException) {
                throw (RuntimeException) e;
            } else if (e instanceof Exception) {
                throw (Exception) e;
            } else {
                throw new SwakExecutionException(e);
            }
        }
    }

    @Override
    protected Object getFallback() {
        Method method = AspectAopUtils.getMethodFromTarget(joinPoint);
        Object obj = joinPoint.getTarget();
        Object[] args = joinPoint.getArgs();
        SwakFallbackMethod fallbackMethod =
            SwakMethodProvider.getInstance().getFallbackMethod(obj.getClass(), method, false);
        FallbackEvent fallbackEvent = SpringBeanFactory.getBean(marsHystrix.event());
        if (fallbackEvent != null) {
            HystrixEventInfo.Builder builder =
                new HystrixEventInfo.Builder().args(args).marsHystrix(marsHystrix).method(method)
                    .target(obj).fallbackMethod(fallbackMethod.getFallbackMethod());
            fallbackEvent.event(builder.build());
        }
        if (fallbackMethod.getMethod() == null) {
            logger.error("No fallback available,fallbackMethod is null,fallbackMethodName:{}",
                fallbackMethod.getFallbackMethod());
            throw new SwakFallBackException("No fallback available,deal with by oneself");
        }
        fallbackMethod.validateReturnType(method);
        try {
            return fallbackMethod.executeWithArgs(obj, args);
        } catch (Throwable e) {
            logger.error("getFallback execute error,fallbackMethodName:"
                + fallbackMethod.getFallbackMethod(), e);
            if (e instanceof RuntimeException) {
                throw (RuntimeException) e;
            } else {
                throw new SwakFallBackException(e.getCause());
            }
        }
    }

    private static Setter setter(ProceedingJoinPoint joinPoint, SwakHystrix marsHystrix) {
        Method method = AopUtils.getMethodFromTarget(joinPoint);
        // 拿到注解
        String className = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = method.getName();

        String methodKey = String.format("%s.%s_%d", className, methodName,
            (joinPoint.getArgs() == null) ? 0 : joinPoint.getArgs().length);
        String threadPoolKey = marsHystrix.threadPoolKey();
        if (StringUtils.isEmpty(threadPoolKey)) {
            threadPoolKey = methodKey;
        }

        int maxQueueSize = marsHystrix.maxQueueSize();
        int queueSizeRejection = marsHystrix.queueSizeRejectionThreshold();
        if (maxQueueSize > -1) {
            queueSizeRejection = Math.max(queueSizeRejection, maxQueueSize);
        }
        return Setter
            .withGroupKey(HystrixCommandGroupKey.Factory
                .asKey(joinPoint.getTarget().getClass().getSimpleName()))
            .andCommandKey(HystrixCommandKey.Factory.asKey(methodKey))
            .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey(threadPoolKey))
            .andCommandPropertiesDefaults(
                HystrixCommandProperties.Setter().withCircuitBreakerRequestVolumeThreshold(20) // 10秒钟内至少19此请求失败，熔断器才发挥起作用
                    .withCircuitBreakerSleepWindowInMilliseconds(30000) // 熔断器中断请求30秒后会进入半打开状态,放部分流量过去重试
                    .withCircuitBreakerErrorThresholdPercentage(50) // 错误率达到50开启熔断保护
                    .withExecutionTimeoutEnabled(marsHystrix.timeoutEnabled()) // 禁用这里的超时
                    .withExecutionTimeoutInMilliseconds(marsHystrix.timeout())
                    .withFallbackIsolationSemaphoreMaxConcurrentRequests(
                        Math.max(marsHystrix.fallbackMaxRequest(), 20))
                    .withExecutionIsolationSemaphoreMaxConcurrentRequests(
                        Math.max(marsHystrix.fallbackMaxRequest(), 20)))
            // 设置线程池参数
            .andThreadPoolPropertiesDefaults(
                HystrixThreadPoolProperties.Setter().withCoreSize(marsHystrix.coreSize())
                    .withMaximumSize(Math.max(marsHystrix.maximumSize(), marsHystrix.coreSize()))
                    .withMaxQueueSize(marsHystrix.maxQueueSize())
                    .withAllowMaximumSizeToDivergeFromCoreSize(true)
                    .withQueueSizeRejectionThreshold(queueSizeRejection));
    }
}
